package it.crosato.stage.server.model.comparison;

import it.crosato.stage.shared.objects.InvariantsGroup;
import it.crosato.stage.shared.objects.Multiset;
import it.crosato.stage.shared.objects.MultisetElement;
import it.crosato.stage.shared.objects.MultisetsGroup;
import it.crosato.stage.shared.objects.Pathway;
import it.crosato.stage.shared.objects.PetriNet;
import it.crosato.stage.shared.objects.Reaction;

import java.util.Vector;

public class InvariantsModeller implements IInvariantsModeller {
	
	@Override
	public MultisetsGroup[][] getPathwaysModels(Pathway[][] pathways,
			boolean enzymes, String sid, String path) {
		
		PetriNet[][] petriNets = getPetriNets(pathways);
		InvariantsGroup[][] invariantsGroups = getInvariantsGroups(petriNets, sid, path);
		if (enzymes) {
			return getEnzymesMultisetsGroups(invariantsGroups, petriNets, pathways);
		}
		else {
			return getReactionsMultisetsGroups(invariantsGroups, petriNets);
		}
	}

	/**
	 * Costruisce le reti di Petri per ogni via metabolica utilizzando l'apposita
	 * funzionalità della classe PetriNet
	 * @param pathways le vie metaboliche
	 * @return le reti di Petri per ogni via metabolica
	 */
	private PetriNet[][] getPetriNets(Pathway[][] pathways) {
		int nOrganisms = pathways.length;
		int nPathways = pathways[0].length;
		PetriNet[][] petriNets = new PetriNet[nOrganisms][nPathways];
		for (int i = 0; i < nOrganisms; i++) {
			for (int j = 0; j < nPathways; j++) {
				petriNets[i][j] = new PetriNet(pathways[i][j]);
			}
		}
		return petriNets;
	}

	/**
	 * Costruisce i gruppi di T-invarianti minimali delle reti di Petri. Utilizza
	 * lo strumento 4ti2 mediante la classe InvariantsCalculator
	 * @param petriNets le reti di petri delle vie metaboliche
	 * @param sessionId id della sessione
	 * @param applicationPath percorso dell'applicazione
	 * @return
	 */
	private InvariantsGroup[][] getInvariantsGroups(PetriNet[][] petriNets,
			String sessionId, String applicationPath) {
		int nOrganisms = petriNets.length;
		int nPathways = petriNets[0].length;
		InvariantsGroup[][] invariantsGroups = new InvariantsGroup[nOrganisms][nPathways];
		for (int i = 0; i < nOrganisms; i++) {
			for (int j = 0; j < nPathways; j++) {
				PetriNet petriNet = petriNets[i][j];
				invariantsGroups[i][j] = InvariantsCalculator.getInvariants(petriNet.getA(),
						sessionId, applicationPath);
			}
		}
		return invariantsGroups;
	}

	/**
	 * Porta i gruppi di T-invarianti minimali sotto forma di gruppi di multi-insiemi
	 * di reazioni chimiche, trasformando ogni invariante in un multi-insieme
	 * @param invariantsGroups i gruppi di T-invarianti minimali che costituiscono le
	 * vie metaboliche
	 * @param petriNets le reti di Petri delle vie metaboliche
	 * @return i gruppi di multi-insiemi di reazioni chimiche che costituiscono le
	 * vie metaboliche
	 */
	private MultisetsGroup[][] getReactionsMultisetsGroups(
			InvariantsGroup[][] invariantsGroups, PetriNet[][] petriNets) {

		int nOrganisms = invariantsGroups.length;
		int nPathways = invariantsGroups[0].length;
		MultisetsGroup[][] multisetsGroups = new MultisetsGroup[nOrganisms][nPathways];
		for (int i = 0; i < nOrganisms; i++) {
			for (int j = 0; j < nPathways; j++) {
				multisetsGroups[i][j] = new MultisetsGroup();
				InvariantsGroup invGroup = invariantsGroups[i][j];
				PetriNet petriNet = petriNets[i][j];
				for (int k = 0; k < invGroup.getNumberOfInvariants(); k++) {
					Multiset multiset = new Multiset();
					for (int h = 0; h < invGroup.getInvariant(k).size(); h++) {
						int cardinality = invGroup.getInvariant(k).get(h);
						String element = petriNet.getT().get(h);
						if (cardinality > 0) {
							multiset.addMultisetElement(new MultisetElement(element, cardinality));
						}
					}
					multisetsGroups[i][j].addMultiset(multiset);
				}
			}
		}
		return multisetsGroups;
	}

	/**
	 * Porta i gruppi di T-invarianti minimali sotto forma di gruppi di multi-insiemi
	 * di enzimi, trasformando ogni invariante in un multi-insieme
	 * @param invariantsGroups i gruppi di T-invarianti minimali che costituiscono le
	 * vie metaboliche
	 * @param petriNets le reti di Petri delle vie metaboliche
	 * @return i gruppi di multi-insiemi di enzimi che costituiscono le vie metaboliche
	 */
	private MultisetsGroup[][] getEnzymesMultisetsGroups(
			InvariantsGroup[][] invariantsGroups, PetriNet[][] petriNets, Pathway[][] pathways) {

		int nOrganisms = invariantsGroups.length;
		int nPathways = invariantsGroups[0].length;
		MultisetsGroup[][] multisetsGroups = new MultisetsGroup[nOrganisms][nPathways];
		for (int i = 0; i < nOrganisms; i++) {
			for (int j = 0; j < nPathways; j++) {
				multisetsGroups[i][j] = new MultisetsGroup();
				InvariantsGroup invGroup = invariantsGroups[i][j];
				PetriNet petriNet = petriNets[i][j];
				Pathway pathway = pathways[i][j];
				for (int k = 0; k < invGroup.getNumberOfInvariants(); k++) {
					Vector<String> enzymes = new Vector<String>();
					for (int h = 0; h < invGroup.getInvariant(k).size(); h++) {
						int reactionCardinality = invGroup.getInvariant(k).get(h);
						for (int l = 0; l < reactionCardinality; l++) {
							String reactionId = petriNet.getT().get(h);
							if (reactionId.charAt(reactionId.length() - 1) == 'r') {
								reactionId = reactionId.substring(0, reactionId.length() - 1);
							}
							Reaction reaction = pathway.getReaction(reactionId);
							Vector<String> reactionEnzymes = reaction.getEnzymes();
							enzymes.addAll(reactionEnzymes);
						}
					}
					multisetsGroups[i][j].addMultiset(new Multiset(enzymes));
				}
			}
		}
		return multisetsGroups;
	}
}
